package com.pig4cloud.pigx.strategy.executor.contract.order.impl;

import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.okex.open.api.bean.account.param.SetLeverage;
import com.okex.open.api.bean.trade.param.ClosePositions;
import com.okex.open.api.bean.trade.param.PlaceOrder;
import com.okex.open.api.config.APIConfiguration;
import com.okex.open.api.customize.constant.ApiOkxConstant;
import com.okex.open.api.customize.enums.ApiOkxEnum;
import com.okex.open.api.customize.util.ApiOkxUtil;
import com.okex.open.api.exception.APIException;
import com.okex.open.api.service.account.AccountAPIService;
import com.okex.open.api.service.account.impl.AccountAPIServiceImpl;
import com.okex.open.api.service.publicData.PublicDataAPIService;
import com.okex.open.api.service.publicData.impl.PublicDataAPIServiceImpl;
import com.okex.open.api.service.trade.TradeAPIService;
import com.okex.open.api.service.trade.impl.TradeAPIServiceImpl;
import com.pig4cloud.pigx.common.customize.contants.SystemConfigConstants;
import com.pig4cloud.pigx.common.customize.exception.ApiRequestException;
import com.pig4cloud.pigx.common.customize.exception.MqAckException;
import com.pig4cloud.pigx.common.customize.exception.MqAckLogException;
import com.pig4cloud.pigx.common.customize.exception.MqNotAckException;
import com.pig4cloud.pigx.common.customize.mysql.utils.MysqlUtil;
import com.pig4cloud.pigx.common.customize.redis.constants.CommonRedisConstants;
import com.pig4cloud.pigx.common.customize.redis.constants.RedisKeyEnum;
import com.pig4cloud.pigx.common.customize.redis.utils.RedisUtils;
import com.pig4cloud.pigx.common.customize.security.utils.SecurityUtil;
import com.pig4cloud.pigx.common.customize.utils.CommonUtil;
import com.pig4cloud.pigx.common.seata.utils.SeataUtils;
import com.pig4cloud.pigx.strategy.bo.StrategyStopOrderContractCacheBO;
import com.pig4cloud.pigx.strategy.bo.StrategyTaskContractCacheBO;
import com.pig4cloud.pigx.strategy.bo.StrategyTaskContractCalculateProfitBO;
import com.pig4cloud.pigx.strategy.bo.okx.PlaceOrderBO;
import com.pig4cloud.pigx.strategy.bo.okx.SyncOrderBO;
import com.pig4cloud.pigx.strategy.dto.StrategyContractCloseDTO;
import com.pig4cloud.pigx.strategy.dto.StrategyContractErrorDTO;
import com.pig4cloud.pigx.strategy.dto.StrategyContractErrorSettlementDTO;
import com.pig4cloud.pigx.strategy.dto.StrategyContractPauseDTO;
import com.pig4cloud.pigx.strategy.dto.StrategyOrderContractErrorDTO;
import com.pig4cloud.pigx.strategy.dto.StrategySyncOrderMsgDTO;
import com.pig4cloud.pigx.strategy.dto.StrategySyncStopOrderMsgDTO;
import com.pig4cloud.pigx.strategy.entity.StrategyContract;
import com.pig4cloud.pigx.strategy.entity.StrategyGroupContract;
import com.pig4cloud.pigx.strategy.entity.StrategyOrderContract;
import com.pig4cloud.pigx.strategy.entity.StrategyPositionContract;
import com.pig4cloud.pigx.strategy.entity.StrategyStopOrderContract;
import com.pig4cloud.pigx.strategy.entity.StrategySwitchContract;
import com.pig4cloud.pigx.strategy.entity.StrategyTaskContract;
import com.pig4cloud.pigx.strategy.enums.StrategyOrderContractEnum;
import com.pig4cloud.pigx.strategy.enums.StrategyPositionContractEnum;
import com.pig4cloud.pigx.strategy.enums.StrategyStopOrderContractEnum;
import com.pig4cloud.pigx.strategy.enums.StrategySwitchContractEnum;
import com.pig4cloud.pigx.strategy.enums.StrategyTaskContractEnum;
import com.pig4cloud.pigx.strategy.executor.contract.order.StrategyOrderContractHandler;
import com.pig4cloud.pigx.strategy.executor.contract.order.StrategyOrderContractHandlerManager;
import com.pig4cloud.pigx.strategy.executor.contract.order.supporter.StrategyOrderContractCalculator;
import com.pig4cloud.pigx.strategy.executor.contract.task.StrategyTaskContractHandler;
import com.pig4cloud.pigx.strategy.executor.contract.task.supporter.StrategyTaskContractCalculator;
import com.pig4cloud.pigx.strategy.mapper.StrategyOrderContractMapper;
import com.pig4cloud.pigx.strategy.mapper.StrategyPositionContractMapper;
import com.pig4cloud.pigx.strategy.mapper.StrategyStopOrderContractMapper;
import com.pig4cloud.pigx.strategy.mapper.StrategySwitchContractMapper;
import com.pig4cloud.pigx.strategy.mapper.StrategyTaskContractMapper;
import com.pig4cloud.pigx.strategy.util.StrategyContractUtil;
import com.pig4cloud.pigx.system.constant.SystemPlatformConstant;
import com.pig4cloud.pigx.system.entity.SystemProduct;
import com.pig4cloud.pigx.user.dto.TransactionThenDTO;
import com.pig4cloud.pigx.user.enums.FuelConsumeEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 欧易订单处理器
 *
 * @since 2022/11/29 17:22
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class StrategyOrderContractOKXHandler implements StrategyOrderContractHandler {

    public static final Long PLATFORM_ID = SystemPlatformConstant.OKX;
    public static final ThreadLocal<APIConfiguration> API_CONFIG = new ThreadLocal<>();
    public static final ThreadLocal<AccountAPIService> ACCOUNT_CLIENT = new ThreadLocal<>();
    public static final ThreadLocal<TradeAPIService> TRADE_CLIENT = new ThreadLocal<>();
    public static final ThreadLocal<PublicDataAPIService> PUBLIC_DATA_CLIENT = new ThreadLocal<>();

    private final StrategyTaskContractHandler strategyTaskContractHandler;

    private final StrategyOrderContractMapper strategyOrderContractMapper;
    private final StrategyTaskContractMapper strategyTaskContractMapper;
    private final StrategySwitchContractMapper strategySwitchContractMapper;
    private final StrategyPositionContractMapper strategyPositionContractMapper;
    private final StrategyStopOrderContractMapper strategyStopOrderContractMapper;
    private final StrategyContractUtil strategyContractUtil;
    private final MysqlUtil mysqlUtil;
    private final SecurityUtil securityUtil;
    private final RedisUtils redisUtils;
    private final RedisTemplate redis;
    private final CommonUtil commonUtil;

    /**
     * 注册处理器
     *
     * @since 2022/12/3 21:12
     */
    @Override
    public void afterPropertiesSet() {
        StrategyOrderContractHandlerManager.HANDLER_LIST.add(this);
    }

    /**
     * 根据平台id判断是否支持
     *
     * @param platformId 平台id
     * @return {@link boolean}
     * @since 2022/11/29 17:27
     */
    @Override
    public boolean support(Long platformId) {
        return PLATFORM_ID.equals(platformId);
    }

    /**
     * 根据用户id、平台id初始化请求参数
     *
     * @param userId     用户id
     * @param platformId 平台id
     * @since 2022/11/30 17:12
     */
    public void initClient(Long userId, Long platformId) throws ApiRequestException {

        APIConfiguration apiConfig = strategyContractUtil.getOkxApiConfig(userId, platformId);
        API_CONFIG.set(apiConfig);

        AccountAPIService accountClient = new AccountAPIServiceImpl(apiConfig);
        ACCOUNT_CLIENT.set(accountClient);

        PublicDataAPIService publicDataClient = new PublicDataAPIServiceImpl(apiConfig);
        PUBLIC_DATA_CLIENT.set(publicDataClient);

        // 交易连接设置为不重试
        apiConfig.setRetryOnConnectionFailure(false);
        TradeAPIService tradeClient = new TradeAPIServiceImpl(apiConfig);
        TRADE_CLIENT.set(tradeClient);
    }

    /**
     * 查询余额
     *
     * @param userId 用户id
     * @return {@link BigDecimal}
     * @since 2022/11/29 17:44
     */
    @Override
    public BigDecimal accountBalance(Long userId) throws ApiRequestException {

        // 初始化客户端
        initClient(userId, PLATFORM_ID);

        JSONObject res = ACCOUNT_CLIENT.get().getBalance("USDT");
        ApiOkxUtil.isSuccess(res);

        JSONArray data = res.getJSONArray("data");
        JSONArray details = data.getJSONObject(0).getJSONArray("details");
        JSONObject detail = details.getJSONObject(0);
        // 可用额度
        BigDecimal availableBalance = detail.getBigDecimal("availEq");
        if (Objects.isNull(availableBalance)) {
            return BigDecimal.ZERO;
        }

        return availableBalance;

    }

    /**
     * 下单
     *
     * @param boList boList
     * @return {@link List< StrategySyncOrderMsgDTO< PlaceOrderBO>>}
     * @since 2022/11/29 17:44
     */
    @Override
    public List<StrategySyncOrderMsgDTO<PlaceOrderBO>> placeOrder(List<PlaceOrderBO> boList) {
        // 获取代理商代码
        String okxBrokerCode = strategyContractUtil.getOKXBrokerCode();
        PlaceOrderBO firstBo = boList.get(0);
        StrategyTaskContractCacheBO taskCache = firstBo.getTaskCache();

        try {
            // 初始化客户端
            initClient(firstBo.getUserId(), PLATFORM_ID);
        } catch (Throwable e) {
            handleErrorOrder(firstBo, e);
            // 上抛异常，结束此次运行
            throw new MqAckException();
        }

        List<StrategySyncOrderMsgDTO<PlaceOrderBO>> syncDTOList = new ArrayList<>(boList.size());
        for (PlaceOrderBO bo : boList) {
            try {
                if (Strings.isNotBlank(okxBrokerCode)) {
                    bo.setRemark("OkxBrokerCode：" + okxBrokerCode);
                }
                boolean isOpen = Objects.equals(StrategyOrderContractEnum.BusinessType.OPEN.getValue(), bo.getBusinessType());
                boolean isClose = Objects.equals(StrategyOrderContractEnum.BusinessType.CLOSE.getValue(), bo.getBusinessType())
                        || Objects.equals(StrategyOrderContractEnum.BusinessType.MANUAL_CLOSE.getValue(), bo.getBusinessType())
                        || Objects.equals(StrategyOrderContractEnum.BusinessType.MANUAL_CLOSE_PART.getValue(), bo.getBusinessType())
                        || Objects.equals(StrategyOrderContractEnum.BusinessType.FORWARD_STOP_CLOSE.getValue(), bo.getBusinessType());
                bo.setOpen(isOpen);
                bo.setClose(isClose);

                // 开启本地事务
                mysqlUtil.startLocalTx();

                // 下单时修改任务
                placeOrderHandleTask(bo);
                // 下单时修改仓位信息
                placeOrderHandlePosition(bo);
                // 新增订单记录
                if (strategyOrderContractMapper.insert(bo) != 1) {
                    throw new MqAckLogException("新增订单记录失败");
                }

                // 开仓首单，设置杠杆倍数
                if (isOpen && bo.isFirstOrder()) {
                    try {
                        JSONObject res = setMultiple(bo);
                        boolean isSuccess = ApiOkxUtil.isSuccess(res, false);
                        if (!isSuccess) {
                            // 回滚本地事务
                            mysqlUtil.rollbackLocalTx();
                            // 下单失败后续处理
                            placeOrderHandleError(bo, res);
                        }
                    } catch (APIException e) {
                        // 设置杠杆倍数发生http异常时，直接标记订单异常
                        throw new MqAckException(e);
                    }
                }

                // 记录下单时是否发生了http异常
                boolean hasHttpError = false;
                try {
                    /*if (bo.isFirstOrder()) {
                        throw new APIException("测试首单http请求失败");
                    }*/
                    /*if (!bo.isFirstOrder()) {
                        throw new APIException("测试第二单http请求失败");
                    }*/

                    JSONObject res;
                    // 平仓、手动平仓时，调用市价全平接口
                    if (Objects.equals(StrategyOrderContractEnum.BusinessType.CLOSE.getValue(), bo.getBusinessType())
                            || Objects.equals(StrategyOrderContractEnum.BusinessType.MANUAL_CLOSE.getValue(), bo.getBusinessType())) {
                        ClosePositions closePositions = new ClosePositions();
                        closePositions.setInstId(bo.getProductName());
                        closePositions.setPosSide(ApiOkxEnum.Side.findName(bo.getSide()));
                        closePositions.setMgnMode(ApiOkxEnum.PositionMode.CROSS.getName());
                        // 自动撤销其它平仓单
                        closePositions.setAutoCxl("true");
                        closePositions.setClOrdId(bo.getId().toString());
                        closePositions.setTag(okxBrokerCode);

                        res = TRADE_CLIENT.get().closePositions(closePositions);

                    } else {
                        // 组装下单参数
                        PlaceOrder placeOrder = new PlaceOrder();
                        placeOrder.setInstId(bo.getProductName());
                        placeOrder.setTdMode(ApiOkxEnum.PositionMode.CROSS.getName());
                        placeOrder.setClOrdId(bo.getId().toString());
                        placeOrder.setSide(ApiOkxUtil.getSide(bo.getSide(), bo.getOperationType()));
                        placeOrder.setPosSide(ApiOkxEnum.Side.findName(bo.getSide()));
                        placeOrder.setOrdType(ApiOkxEnum.Type.findName(bo.getType()));
                        placeOrder.setSz(bo.getNum().toString());
                        placeOrder.setTag(okxBrokerCode);

                        // 下单
                        res = TRADE_CLIENT.get().placeOrder(placeOrder);
                    }

                    boolean isSuccess = ApiOkxUtil.isSuccess(res, false);

                    // 测试下单异常
                    /*if (bo.isFirstOrder()) {
                        isSuccess = false;
                    }*/

                    if (!isSuccess) {
                        // 回滚本地事务
                        mysqlUtil.rollbackLocalTx();
                        // 下单失败后续处理
                        placeOrderHandleError(bo, res);
                    }

                    /*if (bo.isFirstOrder()) {
                        throw new APIException("测试首单http请求超时但下单成功");
                    }*/
                    /*if (!bo.isFirstOrder()) {
                        throw new APIException("测试第二单http请求超时但下单成功");
                    }*/
                } catch (APIException e) {
                    bo.setRemark(e.getMessage());
                    hasHttpError = true;
                }

                // 更新任务缓存、任务JVM缓存
                taskCache = strategyContractUtil.flushTaskCacheByEntity(taskCache, bo.getTask(), null, bo.getPositionList());
                // 缓存订单信息
                redisUtils.flushPersistentCache(RedisKeyEnum.STRATEGY_ORDER_CONTRACT, bo.getId(), bo);

                // 提交本地事务
                mysqlUtil.commitLocalTx();

                // 组装延时同步订单信息mq消息参数
                StrategySyncOrderMsgDTO<PlaceOrderBO> syncOrderMsg = new StrategySyncOrderMsgDTO<>();
                syncOrderMsg.setPlatformId(bo.getPlatformId());
                syncOrderMsg.setOrderId(bo.getId());
                syncOrderMsg.setSwitchId(taskCache.getSwitchId());
                syncDTOList.add(syncOrderMsg);

                // 下单出现http异常，向syncOrderMsg中补充参数
                if (hasHttpError) {
                    syncOrderMsg.setIsHttpError(true);
                    // 清除多余参数
                    clearPlaceOrderBO(bo);
                    syncOrderMsg.setThisOrderBO(bo);

                    // 同时下两个单时，首单出现了http异常
                    if (bo.isFirstOrder() && boList.size() == 2) {
                        // 记录第二个订单的下单参数
                        PlaceOrderBO nextOrderBO = boList.get(1);
                        // 清除多余参数
                        clearPlaceOrderBO(nextOrderBO);
                        syncOrderMsg.setNextOrderBO(nextOrderBO);
                    }

                    // 返回已生成的同步订单消息
                    return syncDTOList;
                }

            } catch (Throwable e) {
                // 回滚本地事务
                mysqlUtil.rollbackLocalTx();
                handleErrorOrder(bo, e);
                e.printStackTrace();
                break;
            }
        }

        // 返回已生成的同步订单消息
        return syncDTOList;
    }

    /**
     * 清除下单参数中不必要的参数
     *
     * @since 2023/2/3 13:57
     */
    private void clearPlaceOrderBO(PlaceOrderBO bo) {
        bo.setTaskCache(null);
        bo.setTask(null);
        bo.setPositionList(null);
    }

    /**
     * 保存错误订单信息
     *
     * @since 2022/12/11 15:07
     */
    public void handleErrorOrder(PlaceOrderBO bo, Throwable e) {
        // 打印日志
        strategyContractUtil.logApiErrorMsg("placeOrder", e, bo);
        // 回滚本地事务
        mysqlUtil.rollbackLocalTx();

        bo.setRemark(e.getMessage());
        bo.setStatus(StrategyOrderContractEnum.Status.ERROR.getValue());

        // 新增订单记录
        if (strategyOrderContractMapper.insert(bo) != 1) {
            throw new MqAckLogException("新增异常订单记录失败");
        }
    }

    /**
     * 下单时修改仓位
     *
     * @param bo bo
     * @since 2022/12/6 18:26
     */
    private void placeOrderHandlePosition(PlaceOrderBO bo) {
        // 查询仓位列表，并锁定
        List<StrategyPositionContract> positionList = strategyContractUtil.getPositionList(bo.getTaskId());
        bo.setPositionList(positionList);
        // 根据方向获取当前仓位
        StrategyPositionContract position = StrategyContractUtil.getCurrentPositionBySide(positionList, bo.getSide(), true);

        // 仓位为空
        if (Objects.isNull(position)) {
            // 操作类型为开仓时，创建新的仓位信息，状态为加仓中
            if (Objects.equals(StrategyOrderContractEnum.OperationType.OPEN.getValue(), bo.getOperationType())) {
                position = new StrategyPositionContract();
                position.setPlatformId(bo.getPlatformId());
                position.setUserId(bo.getUserId());
                position.setTaskId(bo.getTaskId());
                position.setSide(bo.getSide());
                position.setNumOpen(0);
                position.setNumNow(0);
                position.setNumClose(0);
                position.setNumStopOrder(0);
                position.setAvgOpen(BigDecimal.ZERO);
                position.setAvgClose(BigDecimal.ZERO);
                position.setMargin(BigDecimal.ZERO);
                position.setProfit(BigDecimal.ZERO);
                position.setNetProfit(BigDecimal.ZERO);
                position.setErrorSettlementProfit(BigDecimal.ZERO);
                position.setHandlingFee(BigDecimal.ZERO);
                position.setCreateBy(0L);
                position.setUpdateBy(0L);
                position.setStatus(StrategyPositionContractEnum.Status.ADDING.getValue());
                securityUtil.initCreateAbout(position);

                if (strategyPositionContractMapper.add(position) != 1) {
                    throw new MqAckLogException("创建仓位失败");
                }

                positionList.add(position);

            } else {
                // 平仓时抛出异常
                throw new MqNotAckException("数据异常，未能获取到方向对应的仓位");
            }

        } else {
            // 已有仓位信息，校验仓位状态
            if (bo.isOpen()) {
                // 开仓需要仓位处于未持仓状态
                if (!Objects.equals(StrategyPositionContractEnum.Status.EMPTY.getValue(), position.getStatus())) {
                    throw new MqAckLogException("修改仓位记录失败");
                }

            } else if (bo.isClose()) {
                // 平仓、手动平仓、正向止盈平仓需要仓位处于持仓中状态
                if (!Objects.equals(StrategyPositionContractEnum.Status.HOLDING.getValue(), position.getStatus())) {
                    throw new MqAckLogException("修改仓位记录失败");
                }

            } else {
                // 手动开仓、补仓、锁仓、正向止盈回滚需要仓位处于未持仓、持仓中状态
                if (!Objects.equals(StrategyPositionContractEnum.Status.EMPTY.getValue(), position.getStatus())
                        && !Objects.equals(StrategyPositionContractEnum.Status.HOLDING.getValue(), position.getStatus())) {
                    throw new MqAckLogException("修改仓位记录失败");
                }
            }

            // 根据操作类型设置新仓位状态
            String newStatus = Objects.equals(StrategyOrderContractEnum.OperationType.OPEN.getValue(), bo.getOperationType()) ?
                    StrategyPositionContractEnum.Status.ADDING.getValue()
                    : StrategyPositionContractEnum.Status.CLOSING.getValue();
            position.setStatus(newStatus);
            position.setUpdateTime(LocalDateTime.now());
            if (!position.updateById()) {
                throw new MqAckLogException("修改仓位记录失败");
            }
        }

        // 设置订单的仓位id
        bo.setPositionId(position.getId());
    }

    /**
     * 下单时修改任务
     *
     * @param bo bo
     * @since 2022/12/3 14:13
     */
    public void placeOrderHandleTask(PlaceOrderBO bo) {
        // 查询并锁定任务
        QueryWrapper<StrategyTaskContract> queryTask = new QueryWrapper<>();
        queryTask.lambda()
                .eq(StrategyTaskContract::getId, bo.getTaskId())
                .in(StrategyTaskContract::getStatus,
                        StrategyTaskContractEnum.Status.PAUSE.getValue(),
                        StrategyTaskContractEnum.Status.RUNNING.getValue())
                .last("for update");
        StrategyTaskContract task = strategyTaskContractMapper.selectOne(queryTask);
        if (Objects.isNull(task)) {
            throw new MqAckException("修改任务状态失败");
        }
        bo.setTask(task);

        // 补仓、锁仓、正向止盈平仓、正向止盈回滚时，任务不能是暂停状态
        boolean needNotPause = Objects.equals(StrategyOrderContractEnum.BusinessType.ADD.getValue(), bo.getBusinessType())
                || Objects.equals(StrategyOrderContractEnum.BusinessType.LOCK.getValue(), bo.getBusinessType())
                || Objects.equals(StrategyOrderContractEnum.BusinessType.FORWARD_STOP_CLOSE.getValue(), bo.getBusinessType())
                || Objects.equals(StrategyOrderContractEnum.BusinessType.FORWARD_STOP_ROLLBACK.getValue(), bo.getBusinessType());
        if (needNotPause && Objects.equals(StrategyTaskContractEnum.Status.PAUSE.getValue(), task.getStatus())) {
            throw new MqAckLogException("任务状态异常，补仓/锁仓/正向止盈平仓、回滚时状态为暂停，taskId：" + task.getId());
        }

        task.setUpdateTime(LocalDateTime.now());
        task.setUpdateBy(0L);
        task.setUpdateName("系统");
        task.setPendingOrderNum(task.getPendingOrderNum() + 1);
        if (!task.updateById()) {
            throw new MqAckLogException("修改任务状态失败");
        }
    }

    /**
     * 下单失败后续处理
     *
     * @param bo  bo
     * @param res res
     * @since 2022/12/6 17:45
     */
    private void placeOrderHandleError(PlaceOrderBO bo, JSONObject res) {
        StrategyTaskContractCacheBO task = bo.getTaskCache();
        // 开启本地事务
        mysqlUtil.startLocalTx();

        // 获取错误码
        String code = ApiOkxUtil.getResCode(res);
        // 获取错误信息
        String msg = ApiOkxUtil.getResMsg(res);
        // 组装错误信息
        String errorMsg = String.format("错误码：%s，错误信息：%s", code, msg);

        // 开仓
        if (Objects.equals(StrategyOrderContractEnum.BusinessType.OPEN.getValue(), bo.getBusinessType())) {
            // 是首单，关闭策略
            if (bo.isFirstOrder()) {
                StrategyContractCloseDTO dto = new StrategyContractCloseDTO();
                dto.setRemark("开仓第一单失败，错误信息：" + errorMsg);
                dto.setSwitchId(task.getSwitchId());
                dto.setSystem(true);
                strategyTaskContractHandler.close(dto);

            } else {
                // 不是首单，暂停策略
                StrategyContractPauseDTO dto = new StrategyContractPauseDTO();
                dto.setRemark("开仓第二单失败，错误信息：" + errorMsg);
                dto.setSwitchId(task.getSwitchId());
                dto.setSystem(true);
                strategyTaskContractHandler.pause(dto);
            }

        } else if (Objects.equals(StrategyOrderContractEnum.BusinessType.ADD.getValue(), bo.getBusinessType())) {
            // 补仓，暂停策略
            StrategyContractPauseDTO dto = new StrategyContractPauseDTO();
            dto.setRemark("补仓下单失败，错误信息：" + errorMsg);
            dto.setSwitchId(task.getSwitchId());
            dto.setSystem(true);
            strategyTaskContractHandler.pause(dto);

        } else if (Objects.equals(StrategyOrderContractEnum.BusinessType.LOCK.getValue(), bo.getBusinessType())) {
            // 锁仓，暂停策略
            StrategyContractPauseDTO dto = new StrategyContractPauseDTO();
            dto.setRemark("锁仓下单失败，错误信息：" + errorMsg);
            dto.setSwitchId(task.getSwitchId());
            dto.setSystem(true);
            strategyTaskContractHandler.pause(dto);

        } else if (Objects.equals(StrategyOrderContractEnum.BusinessType.FORWARD_STOP_ROLLBACK.getValue(), bo.getBusinessType())) {
            // 正向止盈回滚，暂停策略
            StrategyContractPauseDTO dto = new StrategyContractPauseDTO();
            dto.setRemark("正向止盈回滚下单失败，错误信息：" + errorMsg);
            dto.setSwitchId(task.getSwitchId());
            dto.setSystem(true);
            strategyTaskContractHandler.pause(dto);

        } else if (Objects.equals(StrategyOrderContractEnum.BusinessType.MANUAL_OPEN.getValue(), bo.getBusinessType())) {
            // 手动开仓，暂停策略
            StrategyContractPauseDTO dto = new StrategyContractPauseDTO();
            dto.setRemark("手动开仓下单失败，错误信息：" + errorMsg);
            dto.setSwitchId(task.getSwitchId());
            dto.setSystem(true);
            strategyTaskContractHandler.pause(dto);

        } else if (Objects.equals(StrategyOrderContractEnum.BusinessType.CLOSE.getValue(), bo.getBusinessType())
                || Objects.equals(StrategyOrderContractEnum.BusinessType.MANUAL_CLOSE.getValue(), bo.getBusinessType())
                || Objects.equals(StrategyOrderContractEnum.BusinessType.MANUAL_CLOSE_PART.getValue(), bo.getBusinessType())) {
            // 平仓，标记策略为异常
            StrategyContractErrorDTO dto = new StrategyContractErrorDTO();
            dto.setRemark("平仓下单失败，错误信息：" + errorMsg);
            dto.setSwitchId(task.getSwitchId());
            dto.setSymbolPrice(bo.getPrice());
            strategyTaskContractHandler.error(dto);

        } else if (Objects.equals(StrategyOrderContractEnum.BusinessType.FORWARD_STOP_CLOSE.getValue(), bo.getBusinessType())) {
            // 正向止盈平仓，标记策略为异常
            StrategyContractErrorDTO dto = new StrategyContractErrorDTO();
            dto.setRemark("正向止盈平仓下单失败，错误信息：" + errorMsg);
            dto.setSwitchId(task.getSwitchId());
            dto.setSymbolPrice(bo.getPrice());
            strategyTaskContractHandler.error(dto);
        }

        // 提交本地事务
        mysqlUtil.commitLocalTx();
        // 抛出异常，停止后续下单
        throw new MqAckException(errorMsg);
    }

    /**
     * 设置杠杆倍数
     *
     * @param bo 策略信息
     * @since 2022/11/30 17:12
     */
    private JSONObject setMultiple(PlaceOrderBO bo) {

        // 设置参数
        SetLeverage setLeverage = new SetLeverage();
        setLeverage.setLever(bo.getMultiple().toPlainString());
        setLeverage.setInstId(bo.getProductName());
        // setLeverage.setPosSide(ApiOkxEnum.Side.findName(bo.getSide()));
        setLeverage.setMgnMode(ApiOkxEnum.PositionMode.CROSS.getName());

        // 设置杠杆倍数
        return ACCOUNT_CLIENT.get().setLeverage(setLeverage);

    }

    /**
     * 获取产品信息
     *
     * @param productName 产品名称
     * @return {@link JSONObject}
     * @since 2022/11/30 17:12
     */
    @Override
    public JSONObject getProductInfo(String productName) throws ApiRequestException {

        APIConfiguration apiConfig = strategyContractUtil.getOkxApiConfig(PLATFORM_ID);
        PublicDataAPIServiceImpl service = new PublicDataAPIServiceImpl(apiConfig);

        // 设置参数
        String[] split = productName.split("-");
        String uly = split[0] + "-" + split[1];

        // 设置杠杆倍数
        JSONObject res = service.getInstruments(ApiOkxEnum.InstType.SWAP.getValue(), uly);
        // System.err.println(res);

        // 检查是否成功
        ApiOkxUtil.isSuccess(res);

        JSONArray list = res.getJSONArray(ApiOkxConstant.DATA);
        if (list.isEmpty()) {
            String errorMsg = String.format("未能查询到产品 %s 的信息", productName);
            throw new ApiRequestException(errorMsg);
        }

        return list.getJSONObject(0);

    }

    /**
     * 同步订单状态
     *
     * @param bo bo
     * @since 2022/11/29 17:44
     */
    @Override
    public void syncOrder(SyncOrderBO bo) {
        StrategyOrderContract order = bo.getOrder();
        StrategyContract strategy = bo.getStrategy();
        StrategyTaskContractCacheBO taskCache = bo.getTaskCache();
        SystemProduct product = bo.getProduct();
        // 初始化客户端
        try {
            initClient(order.getUserId(), PLATFORM_ID);
        } catch (ApiRequestException e) {
            throw new MqAckLogException(e);
        }

        // 查询订单
        JSONObject res = TRADE_CLIENT.get().getOrderDetails(product.getName(),
                Objects.isNull(bo.getExternalOrderId()) ? null : bo.getExternalOrderId().toString(),
                Objects.isNull(bo.getOrderId()) ? null : bo.getOrderId().toString());
        // 检查返回结果
        JSONObject orderInfo = checkOrderDetail(bo, res);
        // 如果没有获取到订单信息，会标注订单异常
        if (Objects.isNull(orderInfo)) {
            return;
        }

        // 开启本地事务
        mysqlUtil.startLocalTx();

        // 查询开关，并锁定
        QueryWrapper<StrategySwitchContract> querySwitch = new QueryWrapper<>();
        querySwitch.lambda()
                .eq(StrategySwitchContract::getId, taskCache.getSwitchId())
                .last("for update");
        StrategySwitchContract switchContract = strategySwitchContractMapper.selectOne(querySwitch);
        if (Objects.isNull(switchContract)) {
            throw new MqAckLogException("未能查询到开关信息");
        } else if (Objects.equals(StrategySwitchContractEnum.Status.CLOSE.getValue(), switchContract.getStatus())) {
            throw new MqAckException("开关已关闭");
        }
        bo.setSwitchContract(switchContract);

        // 查询任务，并锁定
        QueryWrapper<StrategyTaskContract> queryTask = new QueryWrapper<>();
        queryTask.lambda()
                .eq(StrategyTaskContract::getId, order.getTaskId())
                .last("for update");
        StrategyTaskContract task = strategyTaskContractMapper.selectOne(queryTask);
        if (Objects.isNull(task)) {
            throw new MqAckLogException("未能查询到任务信息");
        } else if (Objects.equals(StrategyTaskContractEnum.Status.CLOSE.getValue(), task.getStatus())) {
            throw new MqAckException("任务已关闭");
        }
        bo.setTask(task);

        // 同步订单信息
        syncOrder(bo, orderInfo);
        // 未完成，检查同步失败次数
        if (!bo.isOrderComplete()) {
            checkSyncOrderCount(bo);
            return;
        }

        // 处理仓位
        syncOrderHandlePosition(bo, order);
        // 处理任务
        syncOrderHandleTask(bo);
        // 任务完成时，处理开关
        syncOrderHandleSwitch(bo);
        // 处理燃油币
        syncOrderHandleFuel(bo);

        // 任务完成 || 异常
        if (bo.isTaskComplete()) {
            // 删除同步次数累计
            strategyContractUtil.removeSyncCount(order.getId());
            // 删除任务的redis缓存
            redisUtils.delPersistentCache(RedisKeyEnum.STRATEGY_TASK_CONTRACT, task.getId());

        } else if (Objects.isNull(bo.getMsg().getNextOrderBO())
                && (Objects.equals(StrategyOrderContractEnum.BusinessType.CLOSE.getValue(), order.getBusinessType())
                || Objects.equals(StrategyOrderContractEnum.BusinessType.MANUAL_CLOSE.getValue(), order.getBusinessType()))
                && task.getPendingOrderNum() == 0) {
            // 处理市价全平后任务未完成的情况
            syncOrderHandleCloseAllFail(bo);

        } else {
            // 开仓、补仓时，更新lastDealAvg
            BigDecimal lastDealAvg = (Objects.equals(StrategyOrderContractEnum.BusinessType.OPEN.getValue(), order.getBusinessType())
                    || Objects.equals(StrategyOrderContractEnum.BusinessType.ADD.getValue(), order.getBusinessType()))
                    && !Objects.isNull(order.getDealAvg())
                    && order.getDealAvg().compareTo(BigDecimal.ZERO) > 0
                    ? order.getDealAvg()
                    : null;

            // 刷新任务缓存
            taskCache = strategyContractUtil.flushTaskCacheByEntity(taskCache,
                    task,
                    lastDealAvg,
                    bo.getPositionList());
            // 刷新任务JVM缓存
            bo.setTaskCache(taskCache);
        }

        // 删除订单缓存
        redisUtils.delPersistentCache(RedisKeyEnum.STRATEGY_ORDER_CONTRACT, order.getId());

        // 提交分布式事务
        SeataUtils.commitGlobalTx();
        // 提交本地事务
        mysqlUtil.commitLocalTx();

        // 任务正常完成，发送创建新任务消息
        if (bo.isTaskNormalComplete()) {
            strategyContractUtil.sendMqTaskCreate(task.getSwitchId());
        } else if (bo.isTaskComplete() && bo.isTaskError()) {
            // 任务异常状态下完成，发送异常结算消息
            StrategyContractErrorSettlementDTO errorSettlementDTO = new StrategyContractErrorSettlementDTO();
            errorSettlementDTO.setTaskId(task.getId());
            errorSettlementDTO.setStatus(task.getStatus());
            strategyContractUtil.sendMqErrorSettlement(errorSettlementDTO);
        }

        // 发送mq，制度消息
        if (!CollectionUtils.isEmpty(bo.getDividendsMsgList())) {
            bo.getDividendsMsgList().forEach(strategyContractUtil::sendMqDividend);
        }
        // 发送mq，同步止盈止损
        if ((!CollectionUtils.isEmpty(bo.getSyncStopOrderMsgList()))) {
            bo.getSyncStopOrderMsgList().forEach(strategyContractUtil::sendMqSyncStopOrder);
        }
        // 发送mq，策略暂停邮件
        if (bo.isHasPauseWhenLowFuel()) {
            strategyContractUtil.sendEmailStrategyPause(switchContract.getUserId(), strategy, bo.getGroup(), "燃油币账户额度不足");
        }
        // 发送mq，系统日志
        if (!CollectionUtils.isEmpty(bo.getSystemLogList())) {
            bo.getSystemLogList().forEach(strategyContractUtil::sendMqSystemLog);
        }
        // 发送余额告警检查消息
        strategyContractUtil.sendMqFuelWarning(order.getUserId());

        // 任务已完成，不再进行后续逻辑
        if (bo.isTaskComplete()) {
            return;
        }

        // 处理http异常订单的第二个订单
        handleHttpErrorOrderNext(bo);

        // System.err.println("同步订单信息成功");
    }

    /**
     * 同步订单 - 处理市价全平后任务未完成的情况
     *
     * @param bo bo
     * @since 2023/2/20 14:00
     */
    private void syncOrderHandleCloseAllFail(SyncOrderBO bo) {
        StrategyOrderContract order = bo.getOrder();
        StrategyContract strategy = bo.getStrategy();
        StrategyTaskContract task = bo.getTask();
        StrategyTaskContractCacheBO taskCache = bo.getTaskCache();

        List<StrategyStopOrderContractCacheBO> stopOrderList = taskCache.getStopOrderList();
        // 市价全平后，没有止盈止损，标记任务异常
        if (CollectionUtils.isEmpty(stopOrderList)) {
            StrategyContractErrorDTO dto = new StrategyContractErrorDTO();
            dto.setRemark("任务在进行平仓（市价全平）后，仓位数量出现异常");
            dto.setSwitchId(task.getSwitchId());
            dto.setSymbolPrice(order.getPrice());
            strategyTaskContractHandler.error(dto);

        } else {
            // 有止盈止损，筛选待生效、未同步的数据，发送同步消息
            List<StrategyStopOrderContractCacheBO> notSyncStopOrderList = stopOrderList.stream()
                    .filter(stopOrder -> Objects.equals(StrategyStopOrderContractEnum.SyncStatus.NOT_SYNC.getValue(), stopOrder.getSyncStatus()))
                    .collect(Collectors.toList());
            // 没有处于未同步状态的止盈止损
            if (CollectionUtils.isEmpty(notSyncStopOrderList)) {
                // 刷新任务缓存
                taskCache = strategyContractUtil.flushTaskCacheByEntity(taskCache,
                        task,
                        null,
                        bo.getPositionList());
                // 刷新任务JVM缓存
                bo.setTaskCache(taskCache);

            } else {
                // 修改任务数据
                task.setSyncStopOrderNum(task.getSyncStopOrderNum() + notSyncStopOrderList.size());
                if (!task.updateById()) {
                    throw new MqNotAckException("触发止盈止损时，修改任务状态失败");
                }

                // 收集id
                List<Long> stopOrderIdList = notSyncStopOrderList.stream()
                        .map(StrategyStopOrderContractCacheBO::getId)
                        .collect(Collectors.toList());
                // 修改止盈止损
                UpdateWrapper<StrategyStopOrderContract> updateStopOrder = new UpdateWrapper<>();
                updateStopOrder.lambda()
                        .set(StrategyStopOrderContract::getSyncStatus, StrategyStopOrderContractEnum.SyncStatus.RUNNING.getValue())
                        .eq(StrategyStopOrderContract::getSyncStatus, StrategyStopOrderContractEnum.SyncStatus.NOT_SYNC.getValue())
                        .eq(StrategyStopOrderContract::getStatus, StrategyStopOrderContractEnum.Status.NOT_ACTIVE.getValue())
                        .in(StrategyStopOrderContract::getId, stopOrderIdList);
                if (strategyStopOrderContractMapper.update(null, updateStopOrder) != stopOrderIdList.size()) {
                    throw new MqNotAckException("修改触发的止盈止损状态失败");
                }

                // 修改redis缓存
                taskCache = strategyContractUtil.flushTaskCacheByEntity(taskCache,
                        task,
                        null,
                        bo.getPositionList());
                bo.setTaskCache(taskCache);

                // 组装mq消息，同步止盈止损
                List<StrategySyncStopOrderMsgDTO> syncStopOrderMsgList = notSyncStopOrderList.stream()
                        .map(stopOrder -> {
                            StrategySyncStopOrderMsgDTO dto = new StrategySyncStopOrderMsgDTO();
                            dto.setPlatformId(stopOrder.getPlatformId());
                            dto.setExternalId(stopOrder.getExternalId());
                            dto.setStopOrderId(stopOrder.getId());
                            dto.setStrategyId(strategy.getId());
                            dto.setSwitchId(task.getSwitchId());
                            dto.setTaskId(task.getId());
                            dto.setUserId(stopOrder.getUserId());
                            dto.setSideType(stopOrder.getSideType());

                            return dto;
                        }).collect(Collectors.toList());
                bo.setSyncStopOrderMsgList(syncStopOrderMsgList);
            }
        }
    }

    /**
     * 处理http异常订单的第二个订单
     *
     * @param bo bo
     * @since 2023/2/3 10:39
     */
    private boolean handleHttpErrorOrderNext(SyncOrderBO bo) {
        StrategyTaskContractCacheBO taskCache = bo.getTaskCache();
        StrategySyncOrderMsgDTO<PlaceOrderBO> syncDTO = bo.getMsg();
        PlaceOrderBO nextOrderBO = syncDTO.getNextOrderBO();
        // !(是http异常的订单 && 有后续订单 && 任务运行中)
        if (!(syncDTO.getIsHttpError()
                && !Objects.isNull(nextOrderBO)
                && Objects.equals(StrategyTaskContractEnum.Status.RUNNING.getValue(), taskCache.getStatus()))) {
            return false;
        }

        StrategyContract strategy = bo.getStrategy();
        // 获取最新价格
        SystemProduct product = strategyContractUtil.getProductById(strategy.getProductId(), false);
        BigDecimal price = null;
        if (product.getPlatformId().equals(SystemPlatformConstant.OKX)) {
            price = redisUtils.getSymbolPrice(strategy.getSymbolName(), CommonRedisConstants.OKX + CommonRedisConstants.LAST_PRICE);
        }
        if (price.subtract(nextOrderBO.getPrice()).abs().compareTo(strategy.getOrderRetryPriceFluctuation()) > 0) {
            return false;
        }

        // 重新赋值订单参数
        nextOrderBO.setCreateTime(LocalDateTime.now());
        nextOrderBO.setTaskCache(taskCache);
        // 下单
        List<StrategySyncOrderMsgDTO<PlaceOrderBO>> syncDTOList = placeOrder(Collections.singletonList(nextOrderBO));
        syncDTOList.forEach(strategyContractUtil::sendMqSyncOrder);
        return true;
    }

    /**
     * 同步订单时处理仓位
     *
     * @param bo    bo
     * @param order 订单
     * @since 2022/12/7 20:48
     */
    private void syncOrderHandlePosition(SyncOrderBO bo, StrategyOrderContract order) {
        // 查询两个仓位，并锁定
        List<StrategyPositionContract> positionList = strategyContractUtil.getPositionList(order.getTaskId());
        bo.setPositionList(positionList);

        // 根据方向获取订单对应的仓位
        StrategyPositionContract position = StrategyContractUtil.getCurrentPositionBySide(positionList, order.getSide(), false);

        // 操作类型 = 开仓
        if (Objects.equals(StrategyOrderContractEnum.OperationType.OPEN.getValue(), order.getOperationType())) {
            syncOrderHandlePositionAdd(bo, position);

        } else {
            // 操作类型 = 平仓
            syncOrderHandlePositionClose(bo, position);
        }
    }

    /**
     * 检查订单详情返回结果
     *
     * @param bo  bo
     * @param res 返回结果
     * @return {@link JSONObject} 订单详情
     * @since 2022/12/6 21:47
     */
    private JSONObject checkOrderDetail(SyncOrderBO bo,
                                        JSONObject res) {

        StrategyOrderContract order = bo.getOrder();
        StrategyTaskContractCacheBO taskCache = bo.getTaskCache();
        boolean success = ApiOkxUtil.isSuccess(res, false);

        // 测试策略 平仓第二个订单异常后，第一个订单也失败的情景
		/*if (Objects.equals(StrategyOrderContractEnum.BusinessType.CLOSE.getValue(), order.getBusinessType())) {
			success = false;
		}*/

        if (!success) {
            String msg = ApiOkxUtil.getResMsg(res);
            // 设置订单异常
            StrategyOrderContractErrorDTO dto = new StrategyOrderContractErrorDTO();
            dto.setOrder(order);
            dto.setTaskCache(taskCache);
            dto.setRemark(msg);
            strategyTaskContractHandler.errorOrder(dto);

            // 刷新任务JVM缓存
            bo.setTaskCache(dto.getTaskCache());
            // 处理http异常订单
            handleHttpErrorOrder(bo, res);

            return null;
        }

        JSONArray data = res.getJSONArray(ApiOkxConstant.DATA);
        if (data.size() != 1 || Objects.isNull(data.getJSONObject(0))) {
            // 设置订单异常
            StrategyOrderContractErrorDTO dto = new StrategyOrderContractErrorDTO();
            dto.setOrder(order);
            dto.setTaskCache(taskCache);
            dto.setRemark("OK返回订单信息异常，res:" + res);
            strategyTaskContractHandler.errorOrder(dto);
            return null;
        }

        return data.getJSONObject(0);
    }

    /**
     * 处理http异常订单
     * <p> http异常发生时，要么是首个订单，要么是第一个订单已经下单成功，不存在任务是异常的情况
     *
     * @param bo  bo
     * @param res 返回结果
     * @since 2023/2/3 10:01
     */
    private void handleHttpErrorOrder(SyncOrderBO bo,
                                      JSONObject res) {
        StrategyOrderContract order = bo.getOrder();
        StrategyTaskContractCacheBO taskCache = bo.getTaskCache();
        StrategySyncOrderMsgDTO<PlaceOrderBO> syncDTO = bo.getMsg();
        PlaceOrderBO thisOrderBO = syncDTO.getThisOrderBO();
        String code = ApiOkxUtil.getResCode(res);

        // 是否开仓
        boolean isOpen = Objects.equals(StrategyOrderContractEnum.BusinessType.OPEN.getValue(), order.getBusinessType());
        // 是否锁仓
        boolean isLock = Objects.equals(StrategyOrderContractEnum.BusinessType.LOCK.getValue(), order.getBusinessType());
        /*
        !(是http异常的订单 && 订单信息不存在 && 任务运行中 && 开仓（第二单）、补仓、锁仓)
        {"msg":"Order does not exist","code":"51603","data":[]}
        开仓、补仓、锁仓、平仓、正向止盈平仓、正向止盈回滚这些操作中，只有开仓（第二单）、锁仓是需要重试的，其它操作都可以在任务运行中重复发起的
         */
        if (!(syncDTO.getIsHttpError()
                && "51603".equals(code)
                && Objects.equals(StrategyTaskContractEnum.Status.RUNNING.getValue(), taskCache.getStatus())
                && (isOpen && !thisOrderBO.isFirstOrder() || isLock))) {
            return;
        }

        StrategyContract strategy = bo.getStrategy();
        // 源订单下单参数为空，将首个订单作为源订单
        PlaceOrderBO srcOrderBO = thisOrderBO.getSrcOrderBO();
        if (Objects.isNull(thisOrderBO.getSrcOrderBO())) {
            srcOrderBO = BeanUtil.copyProperties(thisOrderBO, PlaceOrderBO.class);
            thisOrderBO.setSrcOrderBO(srcOrderBO);
            thisOrderBO.setSrcId(srcOrderBO.getId());
        }

        // 获取价格
        // 查询产品
        BigDecimal price = null;
        SystemProduct product = strategyContractUtil.getProductById(strategy.getProductId(), false);
        if (product.getPlatformId().equals(SystemPlatformConstant.OKX)) {
            price = redisUtils.getSymbolPrice(strategy.getSymbolName(), CommonRedisConstants.OKX + CommonRedisConstants.LAST_PRICE);
        }
        // 根据源订单价格和订单重试次数判断是否可以重试下单
        if (price.subtract(srcOrderBO.getPrice()).abs().compareTo(strategy.getOrderRetryPriceFluctuation()) > 0
                || order.getRetryCount() + 1 > strategy.getOrderRetryMax()) {
            return;
        }

        // 重新赋值下单参数
        thisOrderBO.setId(commonUtil.getId());
        thisOrderBO.setPrice(price);
        thisOrderBO.setCreateTime(LocalDateTime.now());
        thisOrderBO.setRetryCount(order.getRetryCount() + 1);
        thisOrderBO.setTaskCache(taskCache);
        // 下单
        List<StrategySyncOrderMsgDTO<PlaceOrderBO>> syncDTOList = placeOrder(Collections.singletonList(thisOrderBO));
        syncDTOList.forEach(strategyContractUtil::sendMqSyncOrder);
    }

    /**
     * 同步订单时处理任务
     *
     * @param bo bo
     * @since 2022/12/6 20:32
     */
    private void syncOrderHandleTask(SyncOrderBO bo) {
        StrategyTaskContractCacheBO taskCache = bo.getTaskCache();
        StrategyTaskContract task = bo.getTask();
        StrategyOrderContract order = bo.getOrder();

        // 是否补仓
        boolean isAdd = Objects.equals(StrategyOrderContractEnum.BusinessType.ADD.getValue(), order.getBusinessType());
        // 是否正向止盈平仓
        boolean isForwardStopClose = Objects.equals(StrategyOrderContractEnum.BusinessType.FORWARD_STOP_CLOSE.getValue(), order.getBusinessType());
        // 是否正向止盈回滚
        boolean isForwardStopRollback = Objects.equals(StrategyOrderContractEnum.BusinessType.FORWARD_STOP_ROLLBACK.getValue(), order.getBusinessType());
        boolean isTaskError = Objects.equals(StrategyTaskContractEnum.Status.ERROR.getValue(), task.getStatus());
        bo.setTaskError(isTaskError);

        // 下单手续费
        task.setHandlingFee(task.getHandlingFee().add(order.getHandlingFee()));
        task.setFuelCost(task.getFuelCost().add(order.getFuelHandlingFee()));
        // 下单数量
        if (order.getDealNum() != null && order.getDealNum() > 0) {
            task.setOrderCount(task.getOrderCount() + 1);
        }
        // 减少挂单数量
        task.setPendingOrderNum(task.getPendingOrderNum() - 1);

        // 补仓
        if (isAdd) {
            // 首次补仓
            if (task.getAddCount() == 0) {
                task.setFirstAddSide(order.getSide());
            }
            // 累计补仓次数
            task.setAddCount(task.getAddCount() + 1);
            // 重置缓存中的最高价格波动比例
            taskCache.setHighestFluctuationRatio(BigDecimal.ZERO);
            // 重置正向止盈相关数据
            task.setForwardStopCloseNum(0);
            task.setForwardStopRollbackNum(0);
            taskCache.setForwardStopLowestPrice(BigDecimal.ZERO);
            taskCache.setForwardStopHighestPrice(BigDecimal.ZERO);
        } else if (bo.getIsClose() || isForwardStopClose) {
            // 平仓、手动平仓、止盈、止损、正向止盈平仓
            // 累计利润
            task.setProfit(task.getProfit().add(order.getProfit()));
            // 计算净利润，ok手续费为负数
            task.setNetProfit(task.getProfit().add(task.getHandlingFee()));

            // 正向止盈平仓累计平仓数量
            if (isForwardStopClose) {
                task.setForwardStopCloseNum(task.getForwardStopCloseNum() + order.getDealNum());
            }
        } else if (isForwardStopRollback) {
            // 正向止盈回滚累计回滚数量
            task.setForwardStopRollbackNum(task.getForwardStopRollbackNum() + order.getDealNum());
        }

        // 任务是否完成
        bo.setTaskComplete(StrategyContractUtil.isTaskComplete(bo));
        // 如果任务状态是异常，并且没有挂单和止盈止损，修改异常仓位结算相关数据
        if (bo.isTaskComplete()) {
            if (isTaskError) {
                // 两个平仓单时，触发价格一致
                task.setErrorSettlementPrice(order.getPrice());
                task.setErrorSettlementStatus(StrategyTaskContractEnum.ErrorSettlementStatus.RUNNING.getValue());
            } else {
                // 正常完成
                task.setStatus(StrategyTaskContractEnum.Status.COMPLETE.getValue());
                bo.setTaskNormalComplete(true);
            }
        }

        // 任务正常完成，如果有净利润，计算利润结算
        if (bo.isTaskNormalComplete() && task.getNetProfit().compareTo(BigDecimal.ZERO) > 0) {
            StrategyGroupContract group = bo.getGroup();
            BigDecimal platformPart = StrategyTaskContractCalculator.profitSettlement(task.getNetProfit(), group.getPlatformRatio());
            task.setFuelCost(task.getFuelCost().add(platformPart));
            bo.setProfitSettlement(platformPart);
        }

        if (!task.updateById()) {
            throw new MqAckLogException("修改任务信息失败");
        }
    }

    /**
     * 任务完成时，处理开关
     *
     * @param bo bo
     * @since 2022/12/3 16:53
     */
    private void syncOrderHandleSwitch(SyncOrderBO bo) {
        if (!bo.isTaskComplete()) {
            return;
        }

        StrategyTaskContract task = bo.getTask();
        StrategySwitchContract switchContract = bo.getSwitchContract();
        // System.err.println("毛利润：" + newTask.getProfit());
        // System.err.println("净利润：" + newTask.getNetProfit());
        switchContract.setProfit(switchContract.getProfit().add(task.getProfit()));
        switchContract.setNetProfit(switchContract.getNetProfit().add(task.getNetProfit()));
        switchContract.setFuelCost(switchContract.getFuelCost().add(task.getFuelCost()));
        if (!switchContract.updateById()) {
            throw new MqAckLogException("修改开关失败");
        }
    }

    /**
     * 处理燃油币
     *
     * @param bo bo
     * @since 2022/12/3 16:34
     */
    private void syncOrderHandleFuel(SyncOrderBO bo) {
        StrategyTaskContract task = bo.getTask();
        StrategyOrderContract order = bo.getOrder();
        StrategyContract strategy = bo.getStrategy();
        StrategyGroupContract group = bo.getGroup();
        // 获取策略名称
        String strategyName = StrategyContractUtil.getStrategyName(group, strategy);

        // 开启全局事务
        SeataUtils.startGlobalTx();

        // 扣除利润结算
        if (bo.getProfitSettlement().compareTo(BigDecimal.ZERO) > 0) {
            BigDecimal newFuelBalance = strategyContractUtil.updateFuel(task.getUserId(), FuelConsumeEnum.Type.PROFIT_SETTLEMENT, bo.getProfitSettlement());
            // 生成制度消息
            TransactionThenDTO profitSettlementMsgDto = new TransactionThenDTO();
            profitSettlementMsgDto.setUserId(task.getUserId());
            profitSettlementMsgDto.setOrderId(order.getId());
            profitSettlementMsgDto.setStrategyName(strategyName);
            profitSettlementMsgDto.setFuelType(FuelConsumeEnum.Type.PROFIT_SETTLEMENT);
            profitSettlementMsgDto.setFuel(bo.getProfitSettlement());
            profitSettlementMsgDto.setFuelBalance(newFuelBalance);
            profitSettlementMsgDto.setGrossProfit(task.getProfit());
            profitSettlementMsgDto.setNetProfit(task.getNetProfit());
            bo.getDividendsMsgList().add(profitSettlementMsgDto);

            // 燃油币账户额度不足 && 任务运行中，暂停策略
            if (newFuelBalance.compareTo(BigDecimal.ZERO) <= 0
                    && Objects.equals(StrategyTaskContractEnum.Status.RUNNING.getValue(), task.getStatus())) {
                strategyTaskContractHandler.pauseWhenLowFuel(bo);
            }
        }
    }

    /**
     * 检查同步失败次数
     *
     * @param bo bo
     * @since 2022/12/2 15:03
     */
    private void checkSyncOrderCount(SyncOrderBO bo) {
        // 累计同步次数
        Long syncCount = incrSyncOrderCount(bo.getOrderId());
        // 回滚本地事务
        mysqlUtil.rollbackLocalTx();

        // 每同步失败n次，输出一次日志，该错误理论上不会出现
        if (syncCount % 100 == 0) {
            String errorMsg = String.format("同步订单已累计失败%s次，id：%s", syncCount, bo.getOrderId());
            log.error(errorMsg);
        }

        // 重新发送消息
        strategyContractUtil.sendMqSyncOrder(bo.getMsg());
    }

    /**
     * 累计同步订单次数
     *
     * @param id id
     * @return {@link Long}
     * @since 2022/12/2 15:00
     */
    private Long incrSyncOrderCount(Long id) {
        return redisUtils.addIncrCache(RedisKeyEnum.STRATEGY_ORDER_CONTRACT_SYNC_COUNT, id, 1);
    }

    /**
     * 同步订单状态-加仓
     *
     * @param bo       bo
     * @param position 仓位
     * @since 2022/12/1 19:33
     */
    private void syncOrderHandlePositionAdd(SyncOrderBO bo, StrategyPositionContract position) {
        StrategyOrderContract order = bo.getOrder();
        StrategySyncOrderMsgDTO<PlaceOrderBO> syncDTO = bo.getMsg();

        // 不是止盈止损 && 状态 != 加仓中
        if (!syncDTO.getIsStopOrder()
                && !Objects.equals(StrategyPositionContractEnum.Status.ADDING.getValue(), position.getStatus())) {
            throw new MqAckLogException("加仓时仓位状态不是加仓中");
        }

        position.setUpdateTime(LocalDateTime.now());
        // 有成交数量时，修改以下数据
        if (order.getDealNum() > 0) {
            // 计算新的成交均价
            BigDecimal newDealAvg = StrategyOrderContractCalculator.dealAvg(position.getAvgOpen(),
                    position.getNumNow(),
                    order.getDealAvg(),
                    order.getDealNum());
            position.setNumOpen(position.getNumOpen() + order.getDealNum());
            position.setNumNow(position.getNumNow() + order.getDealNum());
            position.setAvgOpen(newDealAvg);
            position.setHandlingFee(position.getHandlingFee().add(order.getHandlingFee()));
        }

        // 不是止盈止损触发的订单，或同步止盈止损订单时，没有同时进行的其它开平仓操作
        if (!syncDTO.getIsStopOrder()
                || Objects.equals(StrategyPositionContractEnum.Status.HOLDING.getValue(), position.getStatus())) {
            // 无持仓
            if (position.getNumNow() == 0) {
                position.setStatus(StrategyPositionContractEnum.Status.EMPTY.getValue());
            } else {
                // 修改状态为持仓中
                position.setStatus(StrategyPositionContractEnum.Status.HOLDING.getValue());
            }
        }

        if (!position.updateById()) {
            throw new MqAckLogException("修改仓位数据失败");
        }
    }

    /**
     * 同步订单状态-平仓
     *
     * @param bo       bo
     * @param position 仓位
     * @since 2022/12/1 19:33
     */
    private void syncOrderHandlePositionClose(SyncOrderBO bo, StrategyPositionContract position) {
        StrategyOrderContract order = bo.getOrder();
        StrategySyncOrderMsgDTO<PlaceOrderBO> syncDTO = bo.getMsg();

        // 状态 != 平仓中
        if (!syncDTO.getIsStopOrder()
                && !Objects.equals(StrategyPositionContractEnum.Status.CLOSING.getValue(), position.getStatus())) {
            throw new MqAckLogException("平仓时仓位状态不是平仓中");
        }

        position.setUpdateTime(LocalDateTime.now());
        // 有成交数量时，修改以下数据
        if (order.getDealNum() > 0) {
            // 计算新的成交均价
            BigDecimal newDealAvg = StrategyOrderContractCalculator.dealAvg(position.getAvgClose(),
                    position.getNumClose(),
                    order.getDealAvg(),
                    order.getDealNum());
            position.setNumNow(position.getNumNow() - order.getDealNum());
            position.setNumClose(position.getNumClose() + order.getDealNum());
            position.setAvgClose(newDealAvg);
            position.setMargin(position.getMargin().add(order.getMargin()));
            position.setProfit(position.getProfit().add(order.getProfit()));
            position.setHandlingFee(position.getHandlingFee().add(order.getHandlingFee()));
            // ok手续费为负数
            position.setNetProfit(position.getProfit().add(position.getHandlingFee()));
        }

        // 不是止盈止损触发的订单，或同步止盈止损订单时，没有同时进行的其它开平仓操作
        if (!syncDTO.getIsStopOrder()
                || Objects.equals(StrategyPositionContractEnum.Status.HOLDING.getValue(), position.getStatus())) {
            // 修改状态为已平仓，市价全平时，存在平仓数量超过开仓数量的可能（用户自己开仓了）
            if (position.getNumNow() <= 0) {
                position.setStatus(StrategyPositionContractEnum.Status.CLOSE.getValue());
            } else {
                // 修改状态持仓中
                position.setStatus(StrategyPositionContractEnum.Status.HOLDING.getValue());
            }
        }

        if (!position.updateById()) {
            throw new MqAckLogException("修改仓位数据失败");
        }
    }

    /**
     * 处理订单信息
     *
     * @param bo   bo
     * @param data 订单信息
     * @since 2022/12/1 19:46
     */
    private void syncOrder(SyncOrderBO bo, JSONObject data) {
        SystemProduct product = bo.getProduct();
        StrategyOrderContract order = bo.getOrder();

        // 判断订单是否完成
        String state = data.getString("state");
        boolean isCancel = Objects.equals(ApiOkxEnum.OrderState.CANCELED.getValue(), state);
        boolean isDeal = Objects.equals(ApiOkxEnum.OrderState.DEAL.getValue(), state);
        boolean isComplete = isCancel || isDeal;
        boolean isClose = Objects.equals(StrategyOrderContractEnum.BusinessType.CLOSE.getValue(), order.getBusinessType())
                || Objects.equals(StrategyOrderContractEnum.BusinessType.MANUAL_CLOSE.getValue(), order.getBusinessType())
                || Objects.equals(StrategyOrderContractEnum.BusinessType.MANUAL_CLOSE_PART.getValue(), order.getBusinessType())
                || Objects.equals(StrategyOrderContractEnum.BusinessType.STOP_PROFIT.getValue(), order.getBusinessType())
                || Objects.equals(StrategyOrderContractEnum.BusinessType.STOP_LOST.getValue(), order.getBusinessType());
        bo.setIsClose(isClose);
        bo.setOrderComplete(isComplete);
        bo.setIsLong(StrategyContractUtil.isLong(order.getSide()));

        // 未完成直接结束-
        if (!isComplete) {
            throw new MqAckLogException("订单尚未完成");
        }

        // 成交数量
        Integer dealNum = data.getInteger("accFillSz");
        dealNum = Objects.isNull(dealNum) ? 0 : dealNum;
        // 成交均价
        BigDecimal dealAvg = data.getBigDecimal("avgPx");
        dealAvg = Objects.isNull(dealAvg) ? BigDecimal.ZERO : dealAvg.setScale(SystemConfigConstants.amountScale, SystemConfigConstants.mainCurRoundingMode);
        // 手续费
        BigDecimal handlingFee = data.getBigDecimal("fee");
        handlingFee = Objects.isNull(handlingFee) ? BigDecimal.ZERO : handlingFee.setScale(SystemConfigConstants.amountScale, SystemConfigConstants.mainCurRoundingMode);
        // 杠杆倍数
        BigDecimal multiple = data.getBigDecimal("lever");
        // 如果是平仓，累计收益
        if (isClose) {
            BigDecimal profit = data.getBigDecimal("pnl");
            profit = Objects.isNull(profit) ? BigDecimal.ZERO : profit.setScale(SystemConfigConstants.amountScale, SystemConfigConstants.mainCurRoundingMode);
            order.setProfit(profit);
        }

        // 订单完成且没有成交数量，必定是撤销
        if (dealNum == 0) {
            order.setUpdateTime(LocalDateTime.now());
            order.setStatus(StrategyOrderContractEnum.Status.CANCEL.getValue());

        } else {
            // 判断成交状态
            if (isDeal) {
                // 完全成交
                order.setStatus(StrategyOrderContractEnum.Status.DEAL.getValue());

            } else {
                // 欧易部分撤销也是已撤销状态
                int noDealNum = order.getNum() - dealNum;
                if (noDealNum == 0) {
                    order.setStatus(StrategyOrderContractEnum.Status.DEAL.getValue());

                } else {
                    order.setStatus(StrategyOrderContractEnum.Status.CANCEL_PART.getValue());
                }
            }

            BigDecimal orderValue = StrategyOrderContractCalculator.orderValue(dealAvg, dealNum, product.getSheetValue());
            BigDecimal margin = StrategyOrderContractCalculator.standardMargin(orderValue, multiple);
            order.setId(order.getId());
            order.setSheetValue(product.getSheetValue());
            order.setOrderValue(orderValue);
            order.setDealNum(dealNum);
            order.setNoDealNum(order.getNoDealNum() - dealNum);
            order.setDealAvg(dealAvg);
            order.setHandlingFee(handlingFee);
            order.setMargin(margin);
            order.setUpdateTime(LocalDateTime.now());
        }

        // 修改订单信息
        if (strategyOrderContractMapper.syncOrder(order) != 1) {
            throw new MqAckLogException("同步订单信息失败");
        }
    }

    /**
     * 计算收益和收益率数据
     * <p>能计算收益时，肯定已经开过仓了
     *
     * @param task     任务信息
     * @param strategy 策略信息
     * @param product  产品信息
     * @param price    当前币对价格
     * @return {@link StrategyTaskContractCalculateProfitBO}
     * @since 2022/11/28 20:31
     */
    @Override
    public StrategyTaskContractCalculateProfitBO calculateProfit(StrategyTaskContractCacheBO task,
                                                                 StrategyContract strategy,
                                                                 SystemProduct product,
                                                                 BigDecimal price) {

        StrategyTaskContractCalculateProfitBO res = new StrategyTaskContractCalculateProfitBO();
        List<StrategyPositionContract> positionList = task.getPositionList();
        positionList.forEach(i -> {
            // 无持仓
            if (Objects.equals(i.getStatus(), StrategyPositionContractEnum.Status.EMPTY.getValue())) {
                return;
            }

            // 判断是否是多仓
            boolean isLong = StrategyContractUtil.isLong(i.getSide());

            // 有平仓数量，累计已有利润、保证金
            if (i.getNumClose() > 0) {
                if (isLong) {
                    res.setProfitLong(res.getProfitLong().add(i.getProfit()));
                    res.setMarginLong(res.getMarginLong().add(i.getMargin()));
                } else {
                    res.setProfitShort(res.getProfitShort().add(i.getProfit()));
                    res.setMarginShort(res.getMarginShort().add(i.getMargin()));
                }
            }

            // 有持仓数量，计算未实现盈亏、标准保证金
            if (i.getNumNow() > 0) {
                // 利润
                BigDecimal profit = StrategyOrderContractCalculator.profit(i.getSide(),
                        price,
                        i.getAvgOpen(),
                        i.getNumNow(),
                        product.getSheetValue());
                // 全仓，实时计算标准保证金
                BigDecimal orderValue = StrategyOrderContractCalculator.orderValue(price, i.getNumNow(), product.getSheetValue());
                BigDecimal standardMargin = StrategyOrderContractCalculator.standardMargin(orderValue, strategy.getMultiple());

                if (isLong) {
                    res.setProfitLong(res.getProfitLong().add(profit));
                    res.setMarginLong(res.getMarginLong().add(standardMargin));
                } else {
                    res.setProfitShort(res.getProfitShort().add(profit));
                    res.setMarginShort(res.getMarginShort().add(standardMargin));
                }
            }
        });

        // 总收益
        BigDecimal profit = res.getProfitLong().add(res.getProfitShort());
        // 总保证金
        BigDecimal margin = res.getMarginLong().add(res.getMarginShort());
        // 总收益率
        BigDecimal profitRate = StrategyOrderContractCalculator.profitRate(profit, margin);

        res.setIsProfit(profit.compareTo(BigDecimal.ZERO) > 0);
        res.setProfit(profit);
        res.setProfitRate(profitRate);

        return res;
    }

    /**
     * 查询仓位信息
     *
     * @param userId      用户id
     * @param productName 产品名称
     * @return {@link JSONArray}
     * @since 2022/12/8 17:59
     */
    @Override
    public JSONArray positions(Long userId, String productName, String symbolName) throws ApiRequestException {

        APIConfiguration apiConfig = strategyContractUtil.getOkxApiConfig(userId, PLATFORM_ID);
        AccountAPIServiceImpl service = new AccountAPIServiceImpl(apiConfig);

        // 设置参数
//		String[] split = productName.split("-");
//		String uly = split[0] + "-" + split[1];

        // 查询仓位数据
        JSONObject res = service.getPositions(ApiOkxEnum.InstType.SWAP.getValue(), productName, null);

        // 检查是否成功
        ApiOkxUtil.isSuccess(res);

        JSONArray list = res.getJSONArray(ApiOkxConstant.DATA);
        if (list.isEmpty()) {
            throw new ApiRequestException("未能查询到仓位信息");
        }

        return list;
    }
}
